package per.chao.mqtt.infrastructure.util;


import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import com.google.common.collect.Sets;
import org.springframework.util.StringUtils;
import per.chao.mqtt.infrastructure.exception.JacksonException;

import java.io.*;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * Description:
 * <p>
 * Jackson工具类
 * 优势：
 * 数据量高于百万的时候，速度和FastJson相差极小
 * API和注解支持最完善，可定制性最强
 * 支持的数据源最广泛（字符串，对象，文件、流、URL）
 * base: Jackson
 * reference: https://github.com/duanxinyuan/json-utils/blob/master/json-jackson/src/main/java/com/dxy/library/json/jackson/JacksonUtil.java
 *
 * @author W.Chao
 * @date 2020/10/4 11:52
 **/
public final class JacksonUtil {
	private static ObjectMapper om;

	private JacksonUtil() {
	}

	private static final Set<JsonReadFeature> JSON_READ_FEATURES_ENABLED = Sets.newHashSet(
			//允许在JSON中使用Java注释
			JsonReadFeature.ALLOW_JAVA_COMMENTS,
			//允许 json 存在没用双引号括起来的 field
			JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES,
			//允许 json 存在使用单引号括起来的 field
			JsonReadFeature.ALLOW_SINGLE_QUOTES,
			//允许 json 存在没用引号括起来的 ascii 控制字符
			JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS,
			//允许 json number 类型的数存在前导 0 (例: 0001)
			JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS,
			//允许 json 存在 NaN, INF, -INF 作为 number 类型
			JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS,
			//允许 只有Key没有Value的情况
			JsonReadFeature.ALLOW_MISSING_VALUES,
			//允许数组json的结尾多逗号
			JsonReadFeature.ALLOW_TRAILING_COMMA
	);

	static {
		om = initMapper();
	}

	private static ObjectMapper initMapper() {
		JsonMapper.Builder builder = JsonMapper.builder().enable(JSON_READ_FEATURES_ENABLED.toArray(new JsonReadFeature[0]));
		return initMapperConfig(builder);
	}

	private static ObjectMapper initMapperConfig(JsonMapper.Builder builder) {
		ObjectMapper objectMapper = builder.build();
		String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
		objectMapper.setDateFormat(new SimpleDateFormat(dateTimeFormat));
		//配置序列化级别
		objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
		//配置JSON缩进支持
		objectMapper.configure(SerializationFeature.INDENT_OUTPUT, false);
		//允许单个数值当做数组处理
		objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
		//禁止重复键, 抛出异常
		objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
		//禁止使用int代表Enum的order()來反序列化Enum, 抛出异常
		objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);
		//有属性不能映射的时候不报错
		objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
		//对象为空时不抛异常
		objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
		//时间格式
		objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
		//允许未知字段
		objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN);
		//序列化BigDecimal时之间输出原始数字还是科学计数, 默认false, 即是否以toPlainString()科学计数方式来输出
		objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
		//识别Java8时间
		objectMapper.registerModule(new ParameterNamesModule());
		objectMapper.registerModule(new Jdk8Module());
		JavaTimeModule javaTimeModule = new JavaTimeModule();
		javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)))
				.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
		objectMapper.registerModule(javaTimeModule);
		// 识别Guava包的类
		// objectMapper.registerModule(new GuavaModule());
		return objectMapper;
	}

	public static ObjectMapper getObjectMapper() {
		return om;
	}

	/**
	 * JSON反序列化
	 *
	 * @param url
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> T from(URL url, Class<T> type) {
		try {
			return om.readValue(url, type);
		} catch (IOException e) {
			throw new JacksonException("Jackson from error, url: {}, type: {}", url.getPath(), type, e);
		}
	}

	/**
	 * JSON反序列化
	 *
	 * @param url
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> T from(URL url, TypeReference<T> type) {
		try {
			return om.readValue(url, type);
		} catch (IOException e) {
			throw new JacksonException("Jackson from error, url: {}, type: {}", url.getPath(), type, e);
		}
	}

	/**
	 * JSON反序列化(List)
	 *
	 * @param url
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> List<T> fromList(URL url, Class<T> type) {
		CollectionType collectionType = om.getTypeFactory().constructCollectionType(ArrayList.class, type);
		try {
			return om.readValue(url, collectionType);
		} catch (IOException e) {
			throw new JacksonException("Jackson from error, url: {}, type: {}", url.getPath(), type, e);
		}
	}

	/**
	 * JSON反序列化
	 *
	 * @param inputStream
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> T from(InputStream inputStream, Class<T> type) {
		try {
			return om.readValue(inputStream, type);
		} catch (IOException e) {
			throw new JacksonException("Jackson from error, type: {}", type, e);
		}
	}

	/**
	 * JSON反序列化
	 *
	 * @param inputStream
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> T from(InputStream inputStream, TypeReference<T> type) {
		try {
			return om.readValue(inputStream, type);
		} catch (IOException e) {
			throw new JacksonException("Jackson from error, type: {}", type, e);
		}
	}

	/**
	 * JSON反序列化(List)
	 *
	 * @param inputStream
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> List<T> fromList(InputStream inputStream, Class<T> type) {
		CollectionType collectionType = om.getTypeFactory().constructCollectionType(ArrayList.class, type);
		try {
			return om.readValue(inputStream, collectionType);
		} catch (IOException e) {
			throw new JacksonException("Jackson from error, type: {}", type, e);
		}
	}

	/**
	 * JSON反序列化
	 *
	 * @param file
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> T from(File file, Class<T> type) {
		try {
			return om.readValue(file, type);
		} catch (IOException e) {
			throw new JacksonException("Jackson from error, file path: {}, type: {}", file.getParent(), type, e);
		}
	}

	/**
	 * JSON反序列化
	 *
	 * @param file
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> T from(File file, TypeReference<T> type) {
		try {
			return om.readValue(file, type);
		} catch (IOException e) {
			throw new JacksonException("Jackson from error, file path: {}, type: {}", file.getParent(), type, e);
		}
	}

	/**
	 * JSON反序列化(List)
	 *
	 * @param file
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> List<T> fromList(File file, Class<T> type) {
		CollectionType collectionType = om.getTypeFactory().constructCollectionType(ArrayList.class, type);
		try {
			return om.readValue(file, collectionType);
		} catch (IOException e) {
			throw new JacksonException("Jackson from error, file path: {}, type: {}", file.getParent(), type, e);
		}
	}

	/**
	 * JSON反序列化
	 *
	 * @param json
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> T from(String json, Class<T> type) {
		return from(json, (Type) type);
	}

	/**
	 * JSON反序列化
	 *
	 * @param json
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> T from(String json, TypeReference<T> type) {
		return from(json, type);
	}

	/**
	 * JSON反序列化
	 *
	 * @param json
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> T from(String json, Type type) {
		if (StringUtils.isEmpty(json)) {
			return null;
		}

		try {
			JavaType javaType = om.getTypeFactory().constructType(type);
			return om.readValue(json, javaType);
		} catch (JsonProcessingException e) {
			throw new JacksonException("Jackson from error, json: {}, type: {}", json, type, e);
		}
	}

	/**
	 * JSON反序列化(List)
	 *
	 * @param json
	 * @param type
	 * @param <T>
	 * @return
	 */
	public static <T> List<T> fromList(String json, Class<T> type) {
		if (StringUtils.isEmpty(json)) {
			return null;
		}
		try {
			CollectionType collectionType = om.getTypeFactory().constructCollectionType(ArrayList.class, type);
			return om.readValue(json, collectionType);
		} catch (IOException e) {
			throw new JacksonException("Jackson from error, json: {}, type: {}", json, type, e);
		}
	}

	/**
	 * JSON反序列化(Map)
	 *
	 * @param json
	 * @return
	 */
	public static Map<String, Object> fromMap(String json) {
		if (StringUtils.isEmpty(json)) {
			return null;
		}
		try {
			MapType mapType = om.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class);
			return om.readValue(json, mapType);
		} catch (IOException e) {
			throw new JacksonException("Jackson from error, json: {}", json, e);
		}
	}

	/**
	 * 序列化为JSON
	 *
	 * @param list
	 * @param <T>
	 * @return
	 */
	public static <T> String to(List<T> list) {
		try {
			return om.writeValueAsString(list);
		} catch (JsonProcessingException e) {
			throw new JacksonException("Jackson to error, data: {}", list, e);
		}
	}

	/**
	 * 序列化为JSON
	 *
	 * @param t
	 * @param <T>
	 * @return
	 */
	public static <T> String to(T t) {
		try {
			return om.writeValueAsString(t);
		} catch (JsonProcessingException e) {
			throw new JacksonException("Jackson to error, data: {}", t, e);
		}
	}

	/**
	 * 序列化为JSON
	 *
	 * @param path
	 * @param list
	 * @param <T>
	 */
	public static <T> void toFile(String path, List<T> list) {
		try (Writer writer = new FileWriter(new File(path), true)) {
			om.writer().writeValues(writer).writeAll(list);
		} catch (Exception e) {
			throw new JacksonException("Jackson to file error, path: {}, list: {}", path, list, e);
		}
	}

	/**
	 * 序列化为JSON
	 *
	 * @param path
	 * @param t
	 * @param <T>
	 */
	public static <T> void toFile(String path, T t) {
		try (Writer writer = new FileWriter(new File(path), true)) {
			om.writer().writeValues(writer).write(t);
		} catch (Exception e) {
			throw new JacksonException("Jackson to file error, path: {}, data: {}", path, t, e);
		}
	}

	/**
	 * 从json中获取某个字段
	 *
	 * @param json
	 * @param key
	 * @return String, 默认为null
	 */
	public static String getAsString(String json, String key) {
		if (StringUtils.isEmpty(json)) {
			return null;
		}
		try {
			JsonNode jsonObject = getAsJsonObject(json, key);
			if (null == jsonObject) {
				return null;
			}
			return getAsString(jsonObject);
		} catch (Exception e) {
			throw new JacksonException("Jackson get String from json error, json: {}, key: {}", json, key, e);
		}
	}

	/**
	 * 从json中获取某个字段
	 *
	 * @param json
	 * @param key
	 * @return int, 默认为0
	 */
	public static int getAsInt(String json, String key) {
		if (StringUtils.isEmpty(json)) {
			return 0;
		}
		try {
			JsonNode jsonObject = getAsJsonObject(json, key);
			if (null == jsonObject) {
				return 0;
			}
			return jsonObject.isInt() ? jsonObject.intValue() : Integer.parseInt(getAsString(jsonObject));
		} catch (Exception e) {
			throw new JacksonException("Jackson get int from json error, json: {}, key: {}", json, key, e);
		}
	}

	/**
	 * 从json中获取某个字段
	 *
	 * @param json
	 * @param key
	 * @return long, 默认为0L
	 */
	public static long getAsLong(String json, String key) {
		if (StringUtils.isEmpty(json)) {
			return 0L;
		}
		try {
			JsonNode jsonObject = getAsJsonObject(json, key);
			if (null == jsonObject) {
				return 0L;
			}
			return jsonObject.isLong() ? jsonObject.longValue() : Long.parseLong(getAsString(jsonObject));
		} catch (Exception e) {
			throw new JacksonException("Jackson get long from json error, json: {}, key: {}", json, key, e);
		}
	}

	/**
	 * 从json中获取某个字段
	 *
	 * @param json
	 * @param key
	 * @return double, 默认0.0
	 */
	public static double getAsDouble(String json, String key) {
		if (StringUtils.isEmpty(json)) {
			return 0.0;
		}
		try {
			JsonNode jsonObject = getAsJsonObject(json, key);
			if (null == jsonObject) {
				return 0.0;
			}
			return jsonObject.isDouble() ? jsonObject.doubleValue() : Double.parseDouble(getAsString(jsonObject));
		} catch (Exception e) {
			throw new JacksonException("Jackson get double from json error, json: {}, key: {}", json, key, e);
		}
	}

	/**
	 * 从json中获取某个字段
	 *
	 * @param json
	 * @param key
	 * @return BigInteger, 默认0.00
	 */
	public static BigInteger getAsBigInteger(String json, String key) {
		if (StringUtils.isEmpty(json)) {
			return new BigInteger(String.valueOf(0.00));
		}
		try {
			JsonNode jsonObject = getAsJsonObject(json, key);
			if (null == jsonObject) {
				return new BigInteger(String.valueOf(0.00));
			}
			return jsonObject.isBigInteger() ? jsonObject.bigIntegerValue() : new BigInteger(getAsString(jsonObject));
		} catch (Exception e) {
			throw new JacksonException("Jackson get big integer from json error, json: {}, key: {}", json, key, e);
		}
	}

	/**
	 * 从json中获取某个字段
	 *
	 * @param json
	 * @param key
	 * @return BigDecimal, 默认0.00
	 */
	public static BigDecimal getAsBigDecimal(String json, String key) {
		if (StringUtils.isEmpty(json)) {
			return new BigDecimal(String.valueOf(0.00));
		}
		try {
			JsonNode jsonObject = getAsJsonObject(json, key);
			if (null == jsonObject) {
				return new BigDecimal(String.valueOf(0.00));
			}
			return jsonObject.isBigDecimal() ? jsonObject.decimalValue() : new BigDecimal(getAsString(jsonObject));
		} catch (Exception e) {
			throw new JacksonException("Jackson get big decimal from json error, json: {}, key: {}", json, key, e);
		}
	}

	private static final Set<String> TRUE_SET = Sets.newHashSet("true", "yes", "y", "t", "ok", "1", "on", "是", "对", "真", "對", "√");

	/**
	 * 从json中获取某个字段
	 *
	 * @param json
	 * @param key
	 * @return boolean, 默认false
	 */
	public static boolean getAsBoolean(String json, String key) {
		if (StringUtils.isEmpty(json)) {
			return false;
		}
		try {
			JsonNode jsonObject = getAsJsonObject(json, key);
			if (null == jsonObject) {
				return false;
			}
			if (jsonObject.isBoolean()) {
				return jsonObject.booleanValue();
			} else {
				if (jsonObject.isTextual()) {
					return TRUE_SET.contains(jsonObject.textValue().trim().toLowerCase());
				} else {//number
					return TRUE_SET.contains(String.valueOf(jsonObject.intValue()).trim().toLowerCase());
				}
			}
		} catch (Exception e) {
			throw new JacksonException("Jackson get boolean error, json: {}, key: {}", json, key, e);
		}
	}

	/**
	 * 从json中获取某个字段
	 *
	 * @param json
	 * @param key
	 * @return byte[], 默认null
	 */
	public static byte[] getAsBytes(String json, String key) {
		if (StringUtils.isEmpty(json)) {
			return null;
		}
		try {
			JsonNode jsonObject = getAsJsonObject(json, key);
			if (null == jsonObject) {
				return null;
			}
			return jsonObject.isBinary() ? jsonObject.binaryValue() : getAsString(jsonObject).getBytes();
		} catch (Exception e) {
			throw new JacksonException("Jackson get bytes from json error, json: {}, key: {}", json, key, e);
		}
	}

	/**
	 * 从json中获取某个字段
	 *
	 * @param json
	 * @param key
	 * @param type
	 * @param <T>
	 * @return object, 默认null
	 */
	public static <T> T getAsObject(String json, String key, Class<T> type) {
		if (StringUtils.isEmpty(json)) {
			return null;
		}
		try {
			JsonNode jsonObject = getAsJsonObject(json, key);
			if (null == jsonObject) {
				return null;
			}
			JavaType javaType = om.getTypeFactory().constructType(type);
			return from(getAsString(jsonObject), javaType);
		} catch (Exception e) {
			throw new JacksonException("Jackson get list error, json: {}, key: {}, type: {}", json, key, type, e);
		}
	}

	/**
	 * 从json中获取某个字段
	 *
	 * @param json
	 * @param key
	 * @param type
	 * @param <T>
	 * @return list, 默认null
	 */
	public static <T> List<T> getAsList(String json, String key, Class<T> type) {
		if (StringUtils.isEmpty(json)) {
			return null;
		}
		try {
			JsonNode jsonObject = getAsJsonObject(json, key);
			if (null == jsonObject) {
				return null;
			}
			CollectionType collectionType = om.getTypeFactory().constructCollectionType(ArrayList.class, type);
			return from(getAsString(jsonObject), collectionType);
		} catch (Exception e) {
			throw new JacksonException("Jackson get list error, json: {}, key: {}, type: {}", json, key, type, e);
		}
	}

	private static String getAsString(JsonNode jsonObject) {
		return jsonObject.isTextual() ? jsonObject.textValue() : jsonObject.toString();
	}

	public static JsonNode getAsJsonObject(String json, String key) {
		try {
			JsonNode node = om.readTree(json);
			if (null == node) {
				return null;
			}
			return node.get(key);
		} catch (JsonProcessingException e) {
			throw new JacksonException("Jackson get object from json error, json: {}, key: {}", json, key, e);
		}
	}

	/**
	 * 向json中添加属性
	 *
	 * @param json
	 * @param key
	 * @param value
	 * @param <T>
	 * @return
	 */
	public static <T> String add(String json, String key, T value) {
		try {
			JsonNode node = om.readTree(json);
			add(node, key, value);
			return node.toString();
		} catch (IOException e) {
			throw new JacksonException("Jackson add error, json: {}, key: {}, value: {}", json, key, value, e);
		}
	}

	/**
	 * 向json中添加属性
	 *
	 * @param jsonNode
	 * @param key
	 * @param value
	 * @param <T>
	 */
	private static <T> void add(JsonNode jsonNode, String key, T value) {
		if (value instanceof String) {
			((ObjectNode) jsonNode).put(key, (String) value);
		} else if (value instanceof Short) {
			((ObjectNode) jsonNode).put(key, (Short) value);
		} else if (value instanceof Integer) {
			((ObjectNode) jsonNode).put(key, (Integer) value);
		} else if (value instanceof Long) {
			((ObjectNode) jsonNode).put(key, (Long) value);
		} else if (value instanceof Float) {
			((ObjectNode) jsonNode).put(key, (Float) value);
		} else if (value instanceof Double) {
			((ObjectNode) jsonNode).put(key, (Double) value);
		} else if (value instanceof BigDecimal) {
			((ObjectNode) jsonNode).put(key, (BigDecimal) value);
		} else if (value instanceof BigInteger) {
			((ObjectNode) jsonNode).put(key, (BigInteger) value);
		} else if (value instanceof Boolean) {
			((ObjectNode) jsonNode).put(key, (Boolean) value);
		} else if (value instanceof byte[]) {
			((ObjectNode) jsonNode).put(key, (byte[]) value);
		} else {
			((ObjectNode) jsonNode).putPOJO(key, to(value));
		}
	}

	/**
	 * 除去json中的某个属性
	 *
	 * @return json
	 */
	public static String remove(String json, String key) {
		try {
			JsonNode node = om.readTree(json);
			((ObjectNode) node).remove(key);
			return node.toString();
		} catch (IOException e) {
			throw new JacksonException("Jackson remove error, json: {}, key: {}", json, key, e);
		}
	}

	/**
	 * 修改json中的属性
	 */
	public static <T> String update(String json, String key, T value) {
		try {
			JsonNode node = om.readTree(json);
			((ObjectNode) node).remove(key);
			add(node, key, value);
			return node.toString();
		} catch (IOException e) {
			throw new JacksonException("Jackson update error, json: {}, key: {}, value: {}", json, key, value, e);
		}
	}

	/**
	 * 格式化Json(美化)
	 *
	 * @return json
	 */
	public static String format(String json) {
		try {
			JsonNode node = om.readTree(json);
			return om.writerWithDefaultPrettyPrinter().writeValueAsString(node);
		} catch (IOException e) {
			throw new JacksonException("Jackson format json error, json: {}", json, e);
		}
	}

	/**
	 * 判断字符串是否是json
	 *
	 * @return json
	 */
	public static boolean isJson(String json) {
		try {
			om.readTree(json);
			return true;
		} catch (Exception e) {
			return false;
		}
	}
}